#include <iostream>
#include <cstdlib>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <string>
#include <cstring>

#include <cutil_inline.h>
#include "sortingalgorithms.h"

using namespace std;

void helpAndExit(int argc);
int * readArray(char * fileName, unsigned long & cnt);

int main(int argc, char * argv[])
{
    if (argc != 4)
    {
	helpAndExit(argc);
    }
    bool shouldLog = false;
    bool checkCorrectness = true;

    unsigned int blockCnt = atoi(argv[3]);
    char * fileName = argv[2];
    unsigned long cnt;
    int * array = readArray(fileName, cnt);
    int * cudaArray;
    cutilSafeCall(cudaMalloc((void **) &cudaArray, cnt * sizeof(int)));
    cutilSafeCall(cudaMemcpy(cudaArray, array, cnt * sizeof(int), cudaMemcpyHostToDevice));

    if (shouldLog)
    {
	cout << "------ Unsorted Array ------" << endl;
	for(int ii = 0; ii < cnt; ii++)
	    cout << array[ii] << " ";
	cout << endl;
    }
    cudaEvent_t start;
    cudaEvent_t stop;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);

    cudaEventRecord(start, 0);
    if(strcmp("shear", argv[1]) == 0)
    {
	checkCorrectness = false;
	shearSort(cudaArray, cnt, blockCnt);
    } else if(strcmp("bitonic", argv[1]) == 0)
    {
	bitonicSort(cudaArray, cnt, blockCnt);
    } else if (strcmp("evenodd", argv[1]) == 0)
    {
	evenOddMergeSort(cudaArray, cnt, blockCnt);
    } else
    {
	helpAndExit(argc);
    }
    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    cutilSafeCall(cudaMemcpy(array, cudaArray, cnt * sizeof(int), cudaMemcpyDeviceToHost));

    if (shouldLog)
    {
	cout << "------ Sorted Array ------" << endl;
	for(int ii = 0; ii < cnt; ii++)
	    cout << array[ii] << " ";
	cout << endl;
    }
    float duration; // Duration in milliseconds
    cudaEventElapsedTime(&duration, start, stop);
    duration /= 1000;   // Transfer to seconds
    cout << "Time elapsed: " << setprecision(6) << fixed << duration << " s." << endl;
    if (checkCorrectness)
    {
	bool isWrong = false;
	for (int i = 0; i < cnt / 2; i++)
	{
	    if (array[i] > array[i + 1])
	    {
		isWrong = true;
		break;
	    }
	}
	cout << "The array is sorted " << (isWrong ? "incorrectly" : "correctly") << "." << endl;
    }

    delete [] array;
    cutilSafeCall(cudaFree(cudaArray));

    return 0;
}

void helpAndExit(int argc)
{
    cout << "Wrong number of arguments. Expected 3 but given " << argc << endl;
    cout << "Use: SORT_ALG_NAME INPUT_FILE, for example: bitonic array10.txt" << endl;
    cout << "SORT_ALG_NAME = {'shear', 'bitonic', 'evenodd'}" << endl;
    exit(1);
}

int * readArray(char *fileName, unsigned long &cnt)
{
    cout << "Reading the input array..." << endl;
    ifstream file(fileName);
    int * array = NULL;

    if (file.is_open())
    {
	// Read items count
	string line;
	getline(file, line);
	unsigned long count = atol(line.c_str());
	array = new int[count];

	// Read items and fill array
	getline(file, line);
	string numStrs(line);
	istringstream iss(numStrs);
	string numStr;

	if(file.good())
	{
	    for (unsigned long ii=0;ii<count;ii++)
	    {
		getline(iss, numStr, ' ');
		array[ii] = atoi(numStr.c_str());
	    }
	}

	file.close();
	cnt = count;
	cout << "Finished reading, " << cnt << " numbers read." << endl;
	return array;
    } else
    {
	cout << "Unable to read file: " << fileName << endl;
	exit(1);
    }
}
